/**
 * Copyright © 2011,2013 Konstantin Livitski
 * 
 * This file is part of n-Puzzle application. n-Puzzle application is free
 * software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * 
 * n-Puzzle application contains adaptations of artwork covered by the Creative
 * Commons Attribution-ShareAlike 3.0 Unported license. Please refer to the
 * NOTICE.md file at the root of this distribution or repository for licensing
 * terms that apply to that artwork.
 * 
 * n-Puzzle application is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * n-Puzzle application; if not, see the LICENSE/gpl.txt file of this distribution
 * or visit <http://www.gnu.org/licenses>.
 */
package name.livitski.games.puzzle.android.model;

import name.livitski.games.puzzle.android.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;

/**
 * A container for n-puzzle game data.
 */
public class Game implements MoveListener, TileOnTargetListener {

	public static final Level DEFAULT_LEVEL = Level.MEDIUM;
	public static final String DIFFICULTY_SETTING = "difficulty";

	protected static final String BOARD_STATE_SETTING = "tiles";
	protected static final String MOVE_COUNT_SETTING = "move_count";

	private Level difficulty;
	private Board board;
	private int moveCount, score;
	private boolean started;
	private int tileWidth = -1, tileHeight = -1;
	private float imageAspectRatio = Float.NaN;

	/** Returns the difficulty level of this game. */
	public Level getDifficulty() {
		return difficulty;
	}

	/** Returns this game's board. */
	public Board getBoard() {
		return board;
	}

	/** Returns the number of moves made during this game. */
	public int getMoveCount() {
		return moveCount;
	}

	/** Tells whether the puzzle is solved. */
	public boolean isSolved() {
		return board.getTileCount() == score;
	}

	public float getImageAspectRatio() {
		imageAspectRatio = 1;
		if (Float.isNaN(imageAspectRatio))
			throw new IllegalStateException("No image loaded");
		return imageAspectRatio;
	}

	public void setTileSize(int width, int height) {
		tileWidth = width;
		tileHeight = height;
	}

	public boolean isStarted() {
		return started;
	}

	public void preview() {
		if (started)
			throw new IllegalStateException("The game has been started, cannot show a preview");
		board.placeTilesOnTarget();
	}

	public void start() {
		this.moveCount = 0;
		board.placeTilesRandom();
		if (!started) {
			this.score = board.getScore();
			board.addMoveListener(this);
			board.addTileOnTargetListener(this);
		}
		this.started = true;
	}

	public void save(Editor settings) {
		settings.putString(DIFFICULTY_SETTING, getDifficulty().toString());
		if (!isStarted()) {
			settings.remove(MOVE_COUNT_SETTING);
			settings.remove(BOARD_STATE_SETTING);
		} else {
			settings.putInt(MOVE_COUNT_SETTING, getMoveCount());
			settings.putString(BOARD_STATE_SETTING, getBoard().getTileLayout());
		}
	}

	public void load(SharedPreferences state) {
		String layout = state.getString(BOARD_STATE_SETTING, null);
		if (null != layout) {
			moveCount = state.getInt(MOVE_COUNT_SETTING, 0);
			try {
				board.placeTiles(layout);
			} catch (RuntimeException badLayout) {
				Log.w(getClass().getName(), "Error loading game, parsing failed for \"" + BOARD_STATE_SETTING + '"',
						badLayout);
				board.placeTilesRandom();
			}
			if (!started) {
				score = board.getScore();
				board.addMoveListener(this);
				board.addTileOnTargetListener(this);
			}
			this.started = true;
		}
	}

	public void loadImage(final Context context) throws ImageProcessingException {
		if (0 > tileHeight || 0 > tileWidth)
			throw new IllegalStateException("Target size is not set");
		// slice the image
		board.forEachTile(new Board.TileHandler() {
			public void processTile(Tile tile) {
				initTileImage(context, tile);
			}
		});
	}
	
	public void initTileImage(final Context context, Tile tile) {
		Drawable image;
		if (0 == tile.getNumber())
			image = new ColorDrawable(context.getResources().getColor(R.color.blank_tile));
		else {
			Bitmap slice = Bitmap.createBitmap(getImage(context, tile.getNumber()),
					0, 0, tileWidth, tileHeight);
			image = new BitmapDrawable(context.getResources(), slice);
		}
		tile.setDrawable(image);
	}

	private Bitmap getImage(final Context context, Integer number) {
		Integer imageId = number == 1 ? R.drawable.unu : R.drawable.doi;
		return BitmapFactory.decodeResource(context.getResources(), (Integer)imageId);
	}

	public void tileMoved(Tile tile) {
		moveCount++;
	}

	public void tileOnTargetStateChanged(Tile tile, boolean onTarget) {
		score += onTarget ? 1 : -1;
	}

	/**
	 * Creates a new game with specified difficulty level.
	 */
	public Game(Level difficulty) {
		this.difficulty = difficulty;
		board = new Board(difficulty.getBoardSize());
	}

	/**
	 * Creates a new game with the default difficulty level.
	 */
	public Game() {
		this(DEFAULT_LEVEL);
	}

	/**
	 * Difficulty level of a puzzle game.
	 */
	public enum Level {
		EASY(4), MEDIUM(5), HARD(6);

		public int getBoardSize() {
			return boardSize;
		}

		public static Level forBoardSize(int boardSize) {
			if (4 > boardSize || 6 < boardSize)
				throw new IllegalArgumentException("Unsupported board size " + boardSize);
			return values()[boardSize - 3];
		}

		private Level(int boardSize) {
			this.boardSize = boardSize;
		}

		private int boardSize;
	}

	private static final Board.TileHandler TILE_IMAGE_REMOVER = new Board.TileHandler() {
		public void processTile(Tile tile) {
			tile.setDrawable(null);
		}
	};

	public void randomNumber() {
		board.addRandomTile();

	}
}
